1. Préparatifs

J’installe ma session de travail

setwd("~/GitHub/Cours_2020_UniGE/Cours_Geneve_3")
monDossier="~/GitHub/Cours_2020_UniGE/Cours_Geneve_3"
library(reshape2)

Première chose à faire: importer le corpus. Il s’agit d’un csv, donc nous utilisons la fonction read.csv

theatre = "moliere_racine.tsv"
# le paramètre `header` permet de signaler que la première ligne contient le nom des colonnes
# le paramètre `sep` permet d'indiquer comment sont marquées les colonnes. La regex `\t` indique que nous utilisons des tabulations (notre fichier est donc en fait un `tsv`).
theatre <- read.csv(theatre, header=TRUE, sep = "\t", quote = '',fill = TRUE)

Je peux jeter un coup d’œil aux données brutes (on ne m’affiche que les première entrées de chaque colonne par commodité)

str(theatre)
'data.frame':   724 obs. of  8 variables:
 $ auteur     : Factor w/ 5 levels "\"MOLIERE, Jean-Baptiste Pocquelin dit (1622-1673) CORNEILLE, Pierre (1606-1684) QUINAULT, Philippe (1635-1688)\"",..: 2 2 2 2 2 2 2 2 2 2 ...
 $ titre      : Factor w/ 44 levels "\"ALEXANDRE LE GRAND, TRAGÉDIE.\"",..: 2 2 2 2 2 2 2 2 2 2 ...
 $ date       : Factor w/ 24 levels "\"1650\"","\"1655\"",..: 12 12 12 12 12 12 12 12 12 12 ...
 $ genre      : Factor w/ 6 levels "\"Comédie galante\"",..: 3 3 3 3 3 3 3 3 3 3 ...
 $ inspiration: Factor w/ 11 levels "\"bible\"","\"féérie\"",..: 10 10 10 10 10 10 10 10 10 10 ...
 $ structure  : Factor w/ 5 levels "\"Cinq actes\"",..: 3 3 3 3 3 3 3 3 3 3 ...
 $ type       : Factor w/ 3 levels "\"mixte\"","\"prose\"",..: 3 3 3 3 3 3 3 3 3 3 ...
 $ texteLemmat: Factor w/ 724 levels "\" à  bande  de  point  de  Hongrie  appliquer  fort  proprement  sur  un  drap  de  couleur  de  olive  avec  "| __truncated__,..: 629 169 339 272 673 144 82 333 9 306 ...

Je peux aussi les regarder dans un tableau directement dans RStudio. On remarque que les colonnes ont des noms: “auteur”, “titre”…

View(theatre)

Je peux sélectionner juste une colonne (ici “auteur”). Afin de ne pas tout afficher j’utilise head() pour ne montrer que les premières entrées:

head(theatre$auteur)
[1] "MOLIERE, Jean-Baptiste Posquelin dit (1622-1673)"
[2] "MOLIERE, Jean-Baptiste Posquelin dit (1622-1673)"
[3] "MOLIERE, Jean-Baptiste Posquelin dit (1622-1673)"
[4] "MOLIERE, Jean-Baptiste Posquelin dit (1622-1673)"
[5] "MOLIERE, Jean-Baptiste Posquelin dit (1622-1673)"
[6] "MOLIERE, Jean-Baptiste Posquelin dit (1622-1673)"
5 Levels: "MOLIERE, Jean-Baptiste Pocquelin dit (1622-1673) CORNEILLE, Pierre (1606-1684) QUINAULT, Philippe (1635-1688)" ...
# Je peux augmenter le nombre de résultat affiché en indiquant le chiffre souhaité de la manière suivante:
#head(theatre$auteur,10)
#Pour les dernières entrées, il existe une fonction `tail`
#tail(theatre$auteur)

Toutes les colonnes sont des métadonnées, sauf theatre$texteLemmat qui contient des morceaux de pièces de 1000 mots afin de simplifier le travail. Il va falloir transformer le contenu de cette colonne en matrice terme-document (Document Term Matrix): je crée un tableau avec une colonne pour chaque mot de mon corpus, et un rang par texte de mon corpus.

mot1 mot2 mot3
Texte1 1 12 9
Texte2 1 154 4
#Je charge deux nouvelles librairies pour le _text mining_
if(!require("tm")){
  install.packages("tm")
  library("tm")
}
if(!require("tidytext")){
  install.packages("tidytext")
  library("tidytext")
}
corpus <- Corpus(VectorSource(theatre$texteLemmat), readerControl = list(language = "fr"))
corpus
<<SimpleCorpus>>
Metadata:  corpus specific: 1, document level (indexed): 0
Content:  documents: 724
ncol(as.matrix(DocumentTermMatrix(corpus)))
[1] 8896

2. Je nettoie mon corpus

Il est absolument fondamental de nettoyer mon corpus de travail. En effet: “pas” et “Pas” ne sont pas les mêmes chaînes de caractères (il y a une majuscule dans le second), et peut-être même pas les mêmes mots (adverbe ou substantif?). Je dois donc au moins retirer les majuscules (avec tolower()), ou même lemmatiser (de préférence avec un outil spécifique, qui n’existe pas dans R). Ici, nous fournissons le texte déjà lemmatisé pour simplifier le travail

2.1 Les stopwords

Comme notre objectif est d’avoir une approche thématique et conserver des mots potentiellement porteurs de sens: il faut donc retirer tous les mots les plus fréquents qui n’apportent, comme les les pronoms, les pronoms adverbiaux, les prépositions… Ces mots sont appelés des stopwords et une liste est fournie dans la fonction stopwords()

stopwords("french")
  [1] "au"       "aux"      "avec"     "ce"       "ces"      "dans"     "de"       "des"     
  [9] "du"       "elle"     "en"       "et"       "eux"      "il"       "je"       "la"      
 [17] "le"       "leur"     "lui"      "ma"       "mais"     "me"       "même"     "mes"     
 [25] "moi"      "mon"      "ne"       "nos"      "notre"    "nous"     "on"       "ou"      
 [33] "par"      "pas"      "pour"     "qu"       "que"      "qui"      "sa"       "se"      
 [41] "ses"      "son"      "sur"      "ta"       "te"       "tes"      "toi"      "ton"     
 [49] "tu"       "un"       "une"      "vos"      "votre"    "vous"     "c"        "d"       
 [57] "j"        "l"        "à"        "m"        "n"        "s"        "t"        "y"       
 [65] "été"      "étée"     "étées"    "étés"     "étant"    "suis"     "es"       "est"     
 [73] "sommes"   "êtes"     "sont"     "serai"    "seras"    "sera"     "serons"   "serez"   
 [81] "seront"   "serais"   "serait"   "serions"  "seriez"   "seraient" "étais"    "était"   
 [89] "étions"   "étiez"    "étaient"  "fus"      "fut"      "fûmes"    "fûtes"    "furent"  
 [97] "sois"     "soit"     "soyons"   "soyez"    "soient"   "fusse"    "fusses"   "fût"     
[105] "fussions" "fussiez"  "fussent"  "ayant"    "eu"       "eue"      "eues"     "eus"     
[113] "ai"       "as"       "avons"    "avez"     "ont"      "aurai"    "auras"    "aura"    
[121] "aurons"   "aurez"    "auront"   "aurais"   "aurait"   "aurions"  "auriez"   "auraient"
[129] "avais"    "avait"    "avions"   "aviez"    "avaient"  "eut"      "eûmes"    "eûtes"   
[137] "eurent"   "aie"      "aies"     "ait"      "ayons"    "ayez"     "aient"    "eusse"   
[145] "eusses"   "eût"      "eussions" "eussiez"  "eussent"  "ceci"     "cela"     "celà"    
[153] "cet"      "cette"    "ici"      "ils"      "les"      "leurs"    "quel"     "quels"   
[161] "quelle"   "quelles"  "sans"     "soi"     

Il existe des listes alternatives en ligne, plus complètes:

#Donner un nom au fichier que je télécharge
mesStops="stopwords-fr.csv"
#indiquer l'URL où se trouve le document à télécharger
stopword_enLigne = "https://raw.githubusercontent.com/stopwords-iso/stopwords-fr/master/stopwords-fr.txt"
#télécharger le fichier et l'enregistrer sous le nom que je viens de lui donner
download.file(stopword_enLigne,mesStops)
trying URL 'https://raw.githubusercontent.com/stopwords-iso/stopwords-fr/master/stopwords-fr.txt'
Content type 'text/plain; charset=utf-8' length 4566 bytes
==================================================
downloaded 4566 bytes
#Comme c'est un tableur, je le lis avec la fonction adéquat 
stopword_enLigne = read.csv(stopword_enLigne, header=FALSE, stringsAsFactors=FALSE)[,]
#je jette un coup d'œil aux 10 premiers
head(stopword_enLigne,10)
 [1] "a"          "abord"      "absolument" "afin"       "ah"         "ai"         "aie"       
 [8] "aient"      "aies"       "ailleurs"  

Je vais utiliser mes listes de stopwords l’une après l’autre pour nettoyer mon corpus. Pour cela j’utilise la fonction tm_map() qui permet de modifier les corpora. Dans ce cas précise j’utilise removeWords avec chacune des deux listes.

Malheureusement cette commande tm_map fonctionne mal, et il est préférable de nettoyer le texte “à l’ancienne”, en créant sa propore fonction.

#Je recharge mon corpus
corpus_clean <- tm_map(corpus_clean, PlainTextDocument)
transformation drops documents
#je crée une fonction a deux paramètres: le corpus d'entrée et la liste des stopwords.
removeStopWords <- function(corpus_a_nettoyer, stopwords_a_retirer){
  # je fais une boucle pour retirer chaque mot de ```stopwords_a_retirer```
  for (word in stopwords_a_retirer){
    #J'utilise une fonction anonyme (_snonymous function_) à un paramètre qui utilise la fonction ```gsub``` qui remplace le mot de ```stopwords_a_retirer``` par rien.
    removeWord <- function(x) gsub(paste("(^|\\s)(",word,") ", sep="")," ",x)
    #on retire le mot
    corpus_a_nettoyer <- tm_map(corpus_a_nettoyer, removeWord)
  }
  #Je renvoie le résultat
  return(corpus_a_nettoyer)
}
#Je passe mon ```corpus_clean``` comme ```corpus_a_nettoyer``` et mes ```stopword_enLigne``` comme ```stopwords_a_retirer```.
corpus_clean <- removeStopWords(corpus_clean, stopword_enLigne)
transformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documents

S’il reste des mots qui ne me plaisent pas, je peux continuer de les retirer en les mettant dans un vecteur

stopWords <- c( "à_le", "de_le", "-être", "faire", "falloir", "savoir", "pouvoir", "devoir", "devoir", "voir", "vouloir")
corpus_clean <- tm_map(corpus_clean, removeWords, stopWords)
transformation drops documents
inspect(corpus_clean[6])
<<SimpleCorpus>>
Metadata:  corpus specific: 1, document level (indexed): 0
Content:  documents: 1

[1] "  frayeur   âme blesser  moindre choc   entendre     horreur    pensée   jamais  consoler  coup   menacer   laurier   couronne  vainqueur  part     honneur suprême valoir    coûter  tendresse   cœur    moment trembler    aimer       feu   augmenter   marquer  œil  cœur  enflammer    avouer  chose charmant  trouver   amour   objet aimer    oser   scrupule  gêne  tendre sentiment         goûter  amour  Alcmène     entrer        ardeur       faveur   recevoir     qualité     époux  point    donner   nom pourtant   ardeur   brûler   droit  paraître  jour   comprendre   scrupule   embarrasser  amour       ardeur  tendresse passer    époux       moment  doux    délicatesse   concevoir point   cœur  amoureux   petit égard  attacher  étude     inquiétude  manière  heureux   charmant Alcmène    mari    amant  amant   toucher  franchement  sens     mari  gêne  amant   vœu jaloux   point souhaite     cœur  abandonner   passion   point    mari  donner     source obtenir  ardeur      nœud  hyménée    fâcheux    agir  coeur     jour    faveur  douceur empoisonner  scrupule   combattre    satisfaire  délicatesse    séparer    blesser   mari     vertu    cœur  bonté revêtir  amant   amour   tendresse amphitryon  vérité   moquer   langage   peur     croire  sage     écouter  discours  raisonnable Alcmène    penser    long séjour    coupable     port  moment presser adieu    étrange barbarie   temps  arracher    Alcmène       époux songer  amant   prier  séparer point   unir  dieu  époux  amant  fort précieux ciel   aimable caresse   époux ardemment chérir   traître  mari loin   tendresse  nuit    avertir    plier   voile   effacer  étoile  soleil   lit   sortir       quitte          acquitter   amphitryon  aller     brusquerie traître   séparer    fâcherie    temps ensemble demeurer   partir    brutal      douceur  régaler diantre      esprit  aller chercher  faribole  an  mariage épuiser     long temps    regarder traître amphitryon    Alcmène  étaler  flamme  rougir là-dessus    passion   témoigne   femme  Dieu cléantir   amant   âge   passer    seoir   commencement  vieux marier  mauvais grâce     attacher face face pousser  sentiment    perfide  espérer   cœur auprès  soupirer   garde     barbon  oser soupirer   crever  rire mérite  pendard  insigne bonheur     épouse  femme  honneur  Dieu     honnête  grand honneur  valoir   point  femme   rompre    tête   "

Je fais de nouveau une matrice “terme/document” (DTM, Document-term matrix). On se rappelle qu’il s’agit de créer une matrice (un tableau) avec une colonne pour chaque mot de mon corpus, et un rang par texte de mon corpus.

mot1 mot2 mot3
Texte1 1 12 9
Texte2 1 154 4
dtm <- DocumentTermMatrix(corpus_clean)
rownames(dtm) <- theatre$genre

2.2 Les mots peu fréquents

Je peux désormais observer la fréquence des mots: je retrouve la loi de Zipf dans la distribution de mes données

freq <- as.data.frame(colSums(as.matrix(dtm)))
colnames(freq) <- c("frequence")
#Comme je vais dessiner un graph, j'ai besoin d'une nouvelle librairie: ```ggplot2```
if (!require("ggplot2")){
  install.packages("ggplot2")
  library("ggplot2")
}
#Je dessine mon graph
ggplot(freq, aes(x=frequence)) + geom_density()

Je peux compter les mots avec des fréquences faibles, par exemple avec moins de 100 occurrences

#Je retire tous les mots qui apparaissent entre 0 et 400 fois (on peut remplacer 400 par 100, ou même 10 si le corpus est trop gros)
motsPeuFrequents <- findFreqTerms(dtm, 0, 400)
length(motsPeuFrequents)
[1] 8550
head(motsPeuFrequents,50)
 [1] "admirer"     "agir"        "aile"        "aise"        "alcmène"     "allure"      "amoureux"   
 [8] "amphitryon"  "ardeur"      "arrêter"     "artifice"    "asseoir"     "aventure"    "aviser"     
[15] "badinage"    "beauté"      "besoin"      "bout"        "béotique"    "bête"        "cause"      
[22] "censeur"     "cesse"       "chaise"      "chaleur"     "changement"  "charmant"    "cheval"     
[29] "chérir"      "commander"   "comprendre"  "cruel"       "cygne"       "daigner"     "dame"       
[36] "decorum"     "descendre"   "destin"      "devenir"     "deviner"     "divinité"    "don"        
[43] "doucement"   "douceur"     "doute"       "doux"        "déguisement" "délicieux"   "désirer"    
[50] "emploi"     

Je peux aussi compter et afficher les mots les plus fréquents, par exemple avec plus de 400 occurrences

motsTresFrequents <- findFreqTerms(dtm, 401, Inf)
length(motsTresFrequents)
[1] 65
head(motsTresFrequents,50)
 [1] "aimer"     "aller"     "amour"     "attendre"  "chose"     "ciel"      "connaître" "coup"     
 [9] "croire"    "cœur"      "dieu"      "donner"    "foi"       "fort"      "gloire"    "homme"    
[17] "jamais"    "jour"      "mal"       "mettre"    "oui"       "passer"    "penser"    "plaire"   
[25] "point"     "prendre"   "seigneur"  "soin"      "sortir"    "trouver"   "venir"     "âme"      
[33] "œil"       "affaire"   "esprit"    "grand"     "honneur"   "madame"    "monde"     "monsieur" 
[41] "mort"      "nom"       "porter"    "raison"    "temps"     "chercher"  "entendre"  "lieu"     
[49] "main"      "perdre"   

Je fais un très grand ménage, avec une fonction que je crée pour retirer les mots les moins fréquents:

#Je crée une fonction ```grandMenage```
grandMenage <- function(corpus_a_nettoyer, mots_peu_importants){
  #Afin de simplifier le travail (de mon ordinateur), je vais rassembler les mots à retirer en groupe 500 tokens, que je vais traiter séparément.
    chunk <- 500
    #Je compte le nombre de mots à retirer
    n <- length(mots_peu_importants)
    #Je compte les groupes de 500 (ici 17.05), j'arrondis au plus petit entier supérieur (ici 18) 
    r <- rep(1:ceiling(n/chunk),each=chunk)[1:n]
    #Je constitue mes lots sur la base du décompte précédemment mentionné
    d <- split(mots_peu_importants,r)
    #Je fais une boucle: pour retirer les mots du corpus, morceau par morceau
    for (i in 1:length(d)) {
        corpus_a_nettoyer <- tm_map(corpus_a_nettoyer, removeWords, c(paste(d[[i]])))
    }
    #Je renvoie un résultat
    return(corpus_a_nettoyer)
}
# J'utilise ma fonction avec ```corpus_clean``` comme ```corpus_a_nettoyer``` et ```motsPeuFrequents``` comme ```mots_peu_importants```
corpus_clean <- grandMenage(corpus_clean, motsPeuFrequents)
transformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documents

Je redéfinis ma matrice à partir de mon nouveau corpus

dtm <- DocumentTermMatrix(corpus_clean)
rownames(dtm) <- theatre$genre
freq <- as.data.frame(colSums(as.matrix(dtm)))
colnames(freq) <- c("frequence")
#Je fais un petit graph
ggplot(freq, aes(x=frequence)) + geom_density()

Je nettoye un peu ma DTM pour éliminer les rangs vides

rowTotals <- apply(dtm , 1, sum)      #Find the sum of words in each Document
dtm_clean   <- dtm[rowTotals> 0, ]    #remove all docs without words

3. Topic modeling

Remarque préliminaire: le topic modeling requiert des (très) grands corpus, si possible en centaines de documents. Pas de panique cependant: une manière de les obtenir est de diviser chaque textes en plusieurs documents qui forment une unité sémantique. Par exemple le chapitre, la scène, le paragraphe, ou bien (comme c’est le cas pour notre exercice) de 1000 mots.

3.1 Explication théorique

Un thème (topic) est un clusters de mots, i.e. une récurrence de co-occurrence.

100% center

100% center

Source: Wikisource

Le principe du topic modeling est proche de celui de surligner un texte avec plusieurs couleurs: une pour chaque sujet, thème ou topic.

100% center

100% center

Une telle image soulève deux questions sur lesquelles nous reviendront plus tard: * un article peut-il contenir plusieurs sujets? * un mot peut-il n’appartenir qu’à un seul sujet?

Afin de reconnaître ces sujets, on va recourir à une allocation de Dirichlet latente ( Latent Dirichlet allocation, LDA). * C’est une approche non supervisée, c’est-à-dire qu’elle ne nécessite pas d’annotation préalable de données. * Il nous faut définir à l’avance un nombre de sujets/thèmes (infra la variable k)

Le LDA est modèle génératif probabiliste permettant d’expliquer des ensembles d’observations, par le moyen de groupes non observés, eux-mêmes définis par des similarités de données.

150% center

150% center

Source: wikipedia

Dans ce graph: * M est le nombre de documents (corpus) * N est le nombre de mots (document) * W est un mot observé

La partie latente (cachée): * Z est un topic attribué à un w * θ est le mélange des topics à l’échelle du document

Deux paramètres pour la distribution * α est la distribution par document. Si sa valeur est élevée, le document tend à contenir plusieurs topics, si la valeur est faible le nombre de topics est limité * β est la distribution par topic. Si sa valeur est élevée, un même mot se retrouve dans plusieurs topics (qui se ressemblent donc), si la valeur est faible les similarités entre les topics est faible

150% center

150% center

Source: wikipedia

3.2 Une LDA

Le modèle va classer aléatoirement tous les mots en n sujets, et tenter d’affiner cette répartition de manière itérative en observant les contextes:

#J'installe une nouvelle librairie pour le _topic modeling_
if(!require("topicmodels")){
  install.packages("topicmodels")
  library("topicmodels")
}
#Je vais partir sur une classification en deux _topics_
k = 2
lda_2 <- LDA(dtm_clean, k= k, control = list(seed = 1234))
##Je tente avec trois, pour voir…
lda_3 <- LDA(dtm_clean, k= k+1, control = list(alpha = 0.1))

Le résultat produit est une matrice avec pour chaque mot la probabilité qu’il appartienne à un des différents topics. On donne un score β, qui est celui présenté infra.

topics <- tidy(lda_2, matrix = "beta")
topics

3.3 Les paramètres de Gibbs

Les paramètres de Gibbs permettent une sophistication du système précédent. C’est une probabilité conditionnelle qui s’appuie, pour calculer le β d’un mot, sur le β des mots voisins. Pour ce faire nous devons déterminer: 1. À quel point un document aime un topic 2. À quel pount un topic aime un mot

Un document:

Voiture Autoroute Musique Vélo Vacances
1 ?? 2 1 3

Sachant que le décompte est le suivant

topic 1 topic 2 topic 3
Voiture 34 49 75
Autoroute 150 50 70
Musique 34 4 170
Vélo 543 2 150
Vacances 23 70 563

Le topic 1 est le plus représenté dans le document, et Autoroute est déjà surreprésenté dans le décompte, donc on update le tout

Voiture Autoroute Musique Vélo Vacances
1 1 2 1 3
topic 1 topic 2 topic 3
Voiture 34 49 75
Autoroute 151 50 70
Musique 34 4 170
Vélo 543 2 150
Vacances 23 70 563
## Set parameters for Gibbs sampling
#Le modèle va tourner 2000 fois avant de commencer à enregistrer les résultats
burnin <- 2000
#Après cela il va encore tourner 2000 fois
iter <- 2000
# Il ne va enregistrer le résultat que toutes les 500 itérations
thin <- 500
#seed et nstart pour la reproductibilité
SEED=c(1, 2, 3, 4, 5)
seed <-SEED
nstart <- 5
#Seul meilleur modèle est utilisé
best <- TRUE
#2 topics
lda_gibbs_2 <- LDA(dtm_clean, k, method="Gibbs", control=list(nstart=nstart, seed=seed, best=best, burnin=burnin, iter=iter, thin=thin))
#3 topics
lda_gibbs_3 <- LDA(dtm_clean, k+1, method="Gibbs", control=list(nstart=nstart, seed=seed, best=best, burnin=burnin, iter=iter, thin=thin))

Je peux désormais voir les premiers résultats pour chacun des modèles. Il s’agit de de mots dont la fréquence d’utilisation est corrélée

"LDA 2"
[1] "LDA 2"
termsTopic <- as.data.frame(terms(lda_2,10))
head(termsTopic,11)
    Topic 1  Topic 2
1     venir    point
2  monsieur    aller
3     point     cœur
4   prendre    amour
5     aller   donner
6    madame monsieur
7     homme   croire
8    jamais    chose
9       œil   mettre
10     père      oui
"LDA 3"
[1] "LDA 3"
termsTopic <- as.data.frame(terms(lda_3,10))
head(termsTopic,11)
    Topic 1  Topic 2  Topic 3
1  monsieur     fils     cœur
2     point    aller    point
3     aller     dieu    amour
4     venir   madame    aimer
5     chose     sang      œil
6       oui seigneur    aller
7    donner     père seigneur
8   prendre    point    venir
9     homme      œil   madame
10     fort      roi   croire
"LDA GIBBS 2"
[1] "LDA GIBBS 2"
termsTopic <- as.data.frame(terms(lda_gibbs_2,10))
head(termsTopic,11)
    Topic 1  Topic 2
1     point     cœur
2  monsieur   madame
3     aller    amour
4     venir      œil
5    donner    aimer
6   prendre seigneur
7       oui     dieu
8     chose     ciel
9   trouver     jour
10    homme   croire
"LDA GIBBS 3"
[1] "LDA GIBBS 3"
termsTopic <- as.data.frame(terms(lda_gibbs_3,10))
head(termsTopic,11)
   Topic 1  Topic 2  Topic 3
1    point monsieur seigneur
2     cœur    aller     dieu
3   madame      oui      œil
4   croire   donner     fils
5    venir    chose    aller
6    aimer  prendre     main
7    amour    homme     jour
8   jamais  trouver      roi
9      âme     fort     père
10   grand    venir attendre

Nous allons utiliser lda_gibbs_2 et construire une matrice avec les β des tokens (pour les ɣ, et donc des probabilités par document, on aurait mis matrix = "gamma"). Chaque token est répété deux fois, avec une probabilité pour chaque topic:

topics <- tidy(lda_gibbs_2, matrix = "beta")
topics

4. Visualisation

#Je vais encore solliciter une nouvelle librairie
if (!require("dplyr")){
   install.packages("dplyr")
  library("dplyr")
}
#Je récupère mes mots
top_terms <- topics %>%
  group_by(topic) %>%
  top_n(10, beta) %>%
  ungroup()  %>%
  arrange(topic, -beta)
#Je fais un graph
top_terms %>%
  mutate(term = reorder_within(term, beta, topic)) %>%
  ggplot(aes(term, beta, fill = factor(topic))) + geom_col(show.legend = FALSE) +
                                                  facet_wrap(~ topic, scales = "free") +
                                                  coord_flip() +
                                                  scale_x_reordered()

Je vais désormais associer chaque mot à l’un des 5 genres possibles, pour déterminer auquel mes tokens sont rattachés, et découvrir (potentiellement quel genre se cacher derrière quel topic

if (!require("reshape2")){
  install.packages("reshape2")
  library("reshape2")
}
df <- melt(as.matrix(dtm_clean))
df <- df[df$Terms %in% findFreqTerms(dtm_clean, lowfreq = 800), ]
ggplot(df, aes(as.factor(Docs), Terms, fill=log(value))) +
                                             geom_tile() +
                                             xlab("Genres") +
                                             scale_fill_continuous(low="#FEE6CE", high="#E6550D") +
                                             theme(axis.text.x = element_text(angle=90, hjust=1))

tt <- posterior(lda_gibbs_2)$terms
melted = melt(tt[,findFreqTerms(dtm_clean, 1000,10000)])
colnames(melted) <- c("Topics", "Terms", "value")
melted$Topics <- as.factor(melted$Topics)
ggplot(data = melted, aes(x=Topics, y=Terms, fill=value)) + 
                                              geom_tile() +
                                              theme(text = element_text(size=35))

tt <- posterior(lda_gibbs_3)$terms
melted = melt(tt[,findFreqTerms(dtm_clean, 1000,10000)])
colnames(melted) <- c("Topics", "Terms", "value")
melted$Topics <- as.factor(melted$Topics)
ggplot(data = melted, aes(x=Topics, y=Terms, fill=value)) + 
                                              geom_tile() +
                                              theme(text = element_text(size=35))

DocumentTopicProbabilities <- as.data.frame(lda_gibbs_2@gamma)
rownames(DocumentTopicProbabilities) <- rownames(corpus_clean)
head(DocumentTopicProbabilities)
         V1        V2
1 0.4786325 0.5213675
2 0.5083333 0.4916667
3 0.5433071 0.4566929
4 0.5619835 0.4380165
5 0.5357143 0.4642857
6 0.4545455 0.5454545

Nous allons désormais faire des word clouds. Pour cela appelons (installons?) les libraries suivantes:

if (!require("wordcloud")){
   install.packages("wordcloud")
  library("wordcloud")
}
if (!require("RColorBrewer")){
   install.packages("RColorBrewer")
  library("RColorBrewer")
}
if (!require("wordcloud2")){
   install.packages("wordcloud2")
  library("wordcloud2")
}

je récupère les mots et je les associe à leur 𝛃

tm <- posterior(lda_gibbs_2)$terms
data = data.frame(colnames(tm))
head(data)
  colnames.tm.
1        aimer
2      alcmène
3        aller
4        amour
5     attendre
6        chose

Je produis une visualisation par topic

for(topic in seq(k)){
    data$topic <-tm[topic,]
    #text(x=0.5, y=1, paste("V",topic, sep=""),cex=0.6)
    wordcloud(
      words = data$colnames.tm.,
      freq = data$topic,
      #sous ce seuil, les mots ne seront pas affichés
      min.freq=0.0002,
      #nombre maximum de mots à afficher
      max.words=30,
      #Si faux, en ordre croissant
      random.order=FALSE,
      #% de mots à 90°
      rot.per=.35,
      #taille du graph
      scale=c(10,10),
      #couleurs
      colors = brewer.pal(5, "Dark2")
      # il est possible de rentrer directement les couleurs qui nous intéressent
      #c("red", "blue", "yellow", "chartreuse", "cornflowerblue", "darkorange")
    )
}

Finissons avec un peu de mauvais goût, grâce au package wordcloud2

wordcloud2(data = data,
          size=0.4,
          color= "random-light",
          backgroundColor = "pink",
          shape = 'star',
          rotateRatio=1
    )

Rermerciements

Les données d’entraînement ont été créées par JB Camps (ENC). Des morceaux de ce script (notamment pour le nettoyage des données) proviennent d’un cours de Mattia Egloff (UniL).

LS0tCnRpdGxlOiAiQ291cnMgR2Vuw6h2ZSAzIgphdXRob3I6ICJTaW1vbiBHYWJheSIKZGF0ZTogIjEwLzA0LzIwMjAiCm91dHB1dDoKICBpb3NsaWRlc19wcmVzZW50YXRpb246IGRlZmF1bHQKICBodG1sX25vdGVib29rOiBkZWZhdWx0CiAgYmVhbWVyX3ByZXNlbnRhdGlvbjogZGVmYXVsdAogIHNsaWR5X3ByZXNlbnRhdGlvbjogZGVmYXVsdAotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmBgYAoKIyAxLiBQcsOpcGFyYXRpZnMKCkonaW5zdGFsbGUgbWEgc2Vzc2lvbiBkZSB0cmF2YWlsCgpgYGB7cn0Kc2V0d2QoIn4vR2l0SHViL0NvdXJzXzIwMjBfVW5pR0UvQ291cnNfR2VuZXZlXzMiKQptb25Eb3NzaWVyPSJ+L0dpdEh1Yi9Db3Vyc18yMDIwX1VuaUdFL0NvdXJzX0dlbmV2ZV8zIgpgYGAKCmBgYHtyfQpsaWJyYXJ5KHJlc2hhcGUyKQpgYGAKClByZW1pw6hyZSBjaG9zZSDDoCBmYWlyZTogaW1wb3J0ZXIgbGUgY29ycHVzLiBJbCBzJ2FnaXQgZCd1biBjc3YsIGRvbmMgbm91cyB1dGlsaXNvbnMgbGEgZm9uY3Rpb24gYGBgcmVhZC5jc3ZgYGAKCmBgYHtyfQp0aGVhdHJlID0gIm1vbGllcmVfcmFjaW5lLnRzdiIKIyBsZSBwYXJhbcOodHJlIGBoZWFkZXJgIHBlcm1ldCBkZSBzaWduYWxlciBxdWUgbGEgcHJlbWnDqHJlIGxpZ25lIGNvbnRpZW50IGxlIG5vbSBkZXMgY29sb25uZXMKIyBsZSBwYXJhbcOodHJlIGBzZXBgIHBlcm1ldCBkJ2luZGlxdWVyIGNvbW1lbnQgc29udCBtYXJxdcOpZXMgbGVzIGNvbG9ubmVzLiBMYSByZWdleCBgXHRgIGluZGlxdWUgcXVlIG5vdXMgdXRpbGlzb25zIGRlcyB0YWJ1bGF0aW9ucyAobm90cmUgZmljaGllciBlc3QgZG9uYyBlbiBmYWl0IHVuIGB0c3ZgKS4KdGhlYXRyZSA8LSByZWFkLmNzdih0aGVhdHJlLCBoZWFkZXI9VFJVRSwgc2VwID0gIlx0IiwgcXVvdGUgPSAnJyxmaWxsID0gVFJVRSkKYGBgCgpKZSBwZXV4IGpldGVyIHVuIGNvdXAgZCfFk2lsIGF1eCBkb25uw6llcyBicnV0ZXMgKG9uIG5lIG0nYWZmaWNoZSBxdWUgbGVzIHByZW1pw6hyZSBlbnRyw6llcyBkZSBjaGFxdWUgY29sb25uZSBwYXIgY29tbW9kaXTDqSkKCmBgYHtyfQpzdHIodGhlYXRyZSkKYGBgCgpKZSBwZXV4IGF1c3NpIGxlcyByZWdhcmRlciBkYW5zIHVuIHRhYmxlYXUgZGlyZWN0ZW1lbnQgZGFucyBSU3R1ZGlvLiBPbiByZW1hcnF1ZSBxdWUgbGVzIGNvbG9ubmVzIG9udCBkZXMgbm9tczogImF1dGV1ciIsICJ0aXRyZSLigKYKCmBgYHtyfQpWaWV3KHRoZWF0cmUpCmBgYAoKSmUgcGV1eCBzw6lsZWN0aW9ubmVyIGp1c3RlIHVuZSBjb2xvbm5lIChpY2kgImF1dGV1ciIpLiBBZmluIGRlIG5lIHBhcyB0b3V0IGFmZmljaGVyIGondXRpbGlzZSBgYGBoZWFkKClgYGAgcG91ciBuZSBtb250cmVyIHF1ZSBsZXMgcHJlbWnDqHJlcyBlbnRyw6llczoKCmBgYHtyfQpoZWFkKHRoZWF0cmUkYXV0ZXVyKQojIEplIHBldXggYXVnbWVudGVyIGxlIG5vbWJyZSBkZSByw6lzdWx0YXQgYWZmaWNow6kgZW4gaW5kaXF1YW50IGxlIGNoaWZmcmUgc291aGFpdMOpIGRlIGxhIG1hbmnDqHJlIHN1aXZhbnRlOgojaGVhZCh0aGVhdHJlJGF1dGV1ciwxMCkKI1BvdXIgbGVzIGRlcm5pw6hyZXMgZW50csOpZXMsIGlsIGV4aXN0ZSB1bmUgZm9uY3Rpb24gYHRhaWxgCiN0YWlsKHRoZWF0cmUkYXV0ZXVyKQpgYGAKClRvdXRlcyBsZXMgY29sb25uZXMgc29udCBkZXMgbcOpdGFkb25uw6llcywgc2F1ZiBgYGB0aGVhdHJlJHRleHRlTGVtbWF0YGBgIHF1aSBjb250aWVudCBkZXMgbW9yY2VhdXggZGUgcGnDqGNlcyBkZSAxMDAwIG1vdHMgYWZpbiBkZSBzaW1wbGlmaWVyIGxlIHRyYXZhaWwuIElsIHZhIGZhbGxvaXIgdHJhbnNmb3JtZXIgbGUgY29udGVudSBkZSBjZXR0ZSBjb2xvbm5lIGVuIG1hdHJpY2UgdGVybWUtZG9jdW1lbnQgKF9Eb2N1bWVudCBUZXJtIE1hdHJpeF8pOiBqZSBjcsOpZSB1biB0YWJsZWF1IGF2ZWMgdW5lIGNvbG9ubmUgcG91ciBjaGFxdWUgbW90IGRlIG1vbiBjb3JwdXMsIGV0IHVuIHJhbmcgcGFyIHRleHRlIGRlIG1vbiBjb3JwdXMuCgp8ICAgICAgICB8IG1vdDEgfCBtb3QyIHwgbW90MyB8CnwtLS0tLS0tLXwtLS0tLS18LS0tLS0tfC0tLS0tLXwKfCBUZXh0ZTEgfCAgMSAgIHwgIDEyICB8ICA5ICAgfAp8IFRleHRlMiB8ICAxICAgfCAxNTQgIHwgIDQgICB8CgoKYGBge3J9CiNKZSBjaGFyZ2UgZGV1eCBub3V2ZWxsZXMgbGlicmFpcmllcyBwb3VyIGxlIF90ZXh0IG1pbmluZ18KaWYoIXJlcXVpcmUoInRtIikpewogIGluc3RhbGwucGFja2FnZXMoInRtIikKICBsaWJyYXJ5KCJ0bSIpCn0KaWYoIXJlcXVpcmUoInRpZHl0ZXh0IikpewogIGluc3RhbGwucGFja2FnZXMoInRpZHl0ZXh0IikKICBsaWJyYXJ5KCJ0aWR5dGV4dCIpCn0KY29ycHVzIDwtIENvcnB1cyhWZWN0b3JTb3VyY2UodGhlYXRyZSR0ZXh0ZUxlbW1hdCksIHJlYWRlckNvbnRyb2wgPSBsaXN0KGxhbmd1YWdlID0gImZyIikpCmNvcnB1cwpuY29sKGFzLm1hdHJpeChEb2N1bWVudFRlcm1NYXRyaXgoY29ycHVzKSkpCmBgYAoKIyAyLiBKZSBuZXR0b2llIG1vbiBjb3JwdXMKCklsIGVzdCBhYnNvbHVtZW50IGZvbmRhbWVudGFsIGRlIG5ldHRveWVyIG1vbiBjb3JwdXMgZGUgdHJhdmFpbC4gRW4gZWZmZXQ6ICJwYXMiIGV0ICJQYXMiIG5lIHNvbnQgcGFzIGxlcyBtw6ptZXMgY2hhw65uZXMgZGUgY2FyYWN0w6hyZXMgKGlsIHkgYSB1bmUgbWFqdXNjdWxlIGRhbnMgbGUgc2Vjb25kKSwgZXQgcGV1dC3DqnRyZSBtw6ptZSBwYXMgbGVzIG3Dqm1lcyBtb3RzIChhZHZlcmJlIG91IHN1YnN0YW50aWY/KS4gSmUgZG9pcyBkb25jIGF1IG1vaW5zIHJldGlyZXIgbGVzIG1hanVzY3VsZXMgKGF2ZWMgYGBgdG9sb3dlcigpYGBgKSwgb3UgbcOqbWUgbGVtbWF0aXNlciAoZGUgcHLDqWbDqXJlbmNlIGF2ZWMgdW4gb3V0aWwgc3DDqWNpZmlxdWUsIHF1aSBuJ2V4aXN0ZSBwYXMgZGFucyBgYGBSYGBgKS4gSWNpLCBub3VzIGZvdXJuaXNzb25zIGxlIHRleHRlIGTDqWrDoCBsZW1tYXRpc8OpIHBvdXIgc2ltcGxpZmllciBsZSB0cmF2YWlsCgojIyAyLjEgTGVzIF9zdG9wd29yZHNfCgpDb21tZSBub3RyZSBvYmplY3RpZiBlc3QgZCdhdm9pciB1bmUgYXBwcm9jaGUgdGjDqW1hdGlxdWUgZXQgY29uc2VydmVyIGRlcyBtb3RzIHBvdGVudGllbGxlbWVudCBwb3J0ZXVycyBkZSBzZW5zOiBpbCBmYXV0IGRvbmMgcmV0aXJlciB0b3VzIGxlcyBtb3RzIGxlcyBwbHVzIGZyw6lxdWVudHMgcXVpIG4nYXBwb3J0ZW50LCBjb21tZSBsZXMgbGVzIHByb25vbXMsIGxlcyBwcm9ub21zIGFkdmVyYmlhdXgsIGxlcyBwcsOpcG9zaXRpb25z4oCmICBDZXMgbW90cyBzb250IGFwcGVsw6lzIGRlcyBfc3RvcHdvcmRzXyBldCB1bmUgbGlzdGUgZXN0IGZvdXJuaWUgZGFucyBsYSBmb25jdGlvbiBgYGBzdG9wd29yZHMoKWBgYAoKYGBge3J9CnN0b3B3b3JkcygiZnJlbmNoIikKYGBgCgpJbCBleGlzdGUgZGVzIGxpc3RlcyBhbHRlcm5hdGl2ZXMgZW4gbGlnbmUsIHBsdXMgY29tcGzDqHRlczoKCmBgYHtyfQojRG9ubmVyIHVuIG5vbSBhdSBmaWNoaWVyIHF1ZSBqZSB0w6lsw6ljaGFyZ2UKbWVzU3RvcHM9InN0b3B3b3Jkcy1mci5jc3YiCiNpbmRpcXVlciBsJ1VSTCBvw7kgc2UgdHJvdXZlIGxlIGRvY3VtZW50IMOgIHTDqWzDqWNoYXJnZXIKc3RvcHdvcmRfZW5MaWduZSA9ICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vc3RvcHdvcmRzLWlzby9zdG9wd29yZHMtZnIvbWFzdGVyL3N0b3B3b3Jkcy1mci50eHQiCiN0w6lsw6ljaGFyZ2VyIGxlIGZpY2hpZXIgZXQgbCdlbnJlZ2lzdHJlciBzb3VzIGxlIG5vbSBxdWUgamUgdmllbnMgZGUgbHVpIGRvbm5lcgpkb3dubG9hZC5maWxlKHN0b3B3b3JkX2VuTGlnbmUsbWVzU3RvcHMpCiNDb21tZSBjJ2VzdCB1biB0YWJsZXVyLCBqZSBsZSBsaXMgYXZlYyBsYSBmb25jdGlvbiBhZMOpcXVhdCAKc3RvcHdvcmRfZW5MaWduZSA9IHJlYWQuY3N2KHN0b3B3b3JkX2VuTGlnbmUsIGhlYWRlcj1GQUxTRSwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSlbLF0KI2plIGpldHRlIHVuIGNvdXAgZCfFk2lsIGF1eCAxMCBwcmVtaWVycwpoZWFkKHN0b3B3b3JkX2VuTGlnbmUsMTApCmBgYAoKSmUgdmFpcyB1dGlsaXNlciBtZXMgbGlzdGVzIGRlIF9zdG9wd29yZHNfIGwndW5lIGFwcsOocyBsJ2F1dHJlIHBvdXIgbmV0dG95ZXIgbW9uIGNvcnB1cy4gUG91ciBjZWxhIGondXRpbGlzZSBsYSBmb25jdGlvbiBgYGB0bV9tYXAoKWBgYCBxdWkgcGVybWV0IGRlIG1vZGlmaWVyIGxlcyBjb3Jwb3JhLiBEYW5zIGNlIGNhcyBwcsOpY2lzZSBqJ3V0aWxpc2UgYGBgcmVtb3ZlV29yZHNgYGAgYXZlYyBjaGFjdW5lIGRlcyBkZXV4IGxpc3Rlcy4KCmBgYHtyLGlnbm9yZSA9IFRSVUUsIGluY2x1ZGUgPSBGQUxTRX0KY29ycHVzX2NsZWFuIDwtIHRtX21hcChjb3JwdXMsIHJlbW92ZVdvcmRzLCBzdG9wd29yZHMoImZyZW5jaCIpKQpjb3JwdXNfY2xlYW4gPC0gdG1fbWFwKGNvcnB1cywgcmVtb3ZlV29yZHMsIHN0b3B3b3JkX2VuTGlnbmUpCiNKZSBqZXR0ZSB1biBjb3VwIGQnxZNpbCDDoCBsYSBzaXhpw6htZSBlbnRyw6llIHBvdXIgY29udHLDtGxlciBxdWUgdG91dCBlc3QgZW4gb3JkcmUKaW5zcGVjdChjb3JwdXNfY2xlYW5bNl0pCmBgYAoKTWFsaGV1cmV1c2VtZW50IGNldHRlIGNvbW1hbmRlIGBgYHRtX21hcGBgYCBmb25jdGlvbm5lIG1hbCwgZXQgaWwgZXN0IHByw6lmw6lyYWJsZSBkZSBuZXR0b3llciBsZSB0ZXh0ZSAiw6AgbCdhbmNpZW5uZSIsIGVuIGNyw6lhbnQgc2EgcHJvcG9yZSBmb25jdGlvbi4KCmBgYHtyfQojSmUgcmVjaGFyZ2UgbW9uIGNvcnB1cwpjb3JwdXNfY2xlYW4gPC0gdG1fbWFwKGNvcnB1c19jbGVhbiwgUGxhaW5UZXh0RG9jdW1lbnQpCiNqZSBjcsOpZSB1bmUgZm9uY3Rpb24gYSBkZXV4IHBhcmFtw6h0cmVzOiBsZSBjb3JwdXMgZCdlbnRyw6llIGV0IGxhIGxpc3RlIGRlcyBzdG9wd29yZHMuCnJlbW92ZVN0b3BXb3JkcyA8LSBmdW5jdGlvbihjb3JwdXNfYV9uZXR0b3llciwgc3RvcHdvcmRzX2FfcmV0aXJlcil7CiAgIyBqZSBmYWlzIHVuZSBib3VjbGUgcG91ciByZXRpcmVyIGNoYXF1ZSBtb3QgZGUgYGBgc3RvcHdvcmRzX2FfcmV0aXJlcmBgYAogIGZvciAod29yZCBpbiBzdG9wd29yZHNfYV9yZXRpcmVyKXsKICAgICNKJ3V0aWxpc2UgdW5lIGZvbmN0aW9uIGFub255bWUgKF9zbm9ueW1vdXMgZnVuY3Rpb25fKSDDoCB1biBwYXJhbcOodHJlIHF1aSB1dGlsaXNlIGxhIGZvbmN0aW9uIGBgYGdzdWJgYGAgcXVpIHJlbXBsYWNlIGxlIG1vdCBkZSBgYGBzdG9wd29yZHNfYV9yZXRpcmVyYGBgIHBhciByaWVuLgogICAgcmVtb3ZlV29yZCA8LSBmdW5jdGlvbih4KSBnc3ViKHBhc3RlKCIoXnxcXHMpKCIsd29yZCwiKSAiLCBzZXA9IiIpLCIgIix4KQogICAgI29uIHJldGlyZSBsZSBtb3QKICAgIGNvcnB1c19hX25ldHRveWVyIDwtIHRtX21hcChjb3JwdXNfYV9uZXR0b3llciwgcmVtb3ZlV29yZCkKICB9CiAgI0plIHJlbnZvaWUgbGUgcsOpc3VsdGF0CiAgcmV0dXJuKGNvcnB1c19hX25ldHRveWVyKQp9CgojSmUgcGFzc2UgbW9uIGBgYGNvcnB1c19jbGVhbmBgYCBjb21tZSBgYGBjb3JwdXNfYV9uZXR0b3llcmBgYCBldCBtZXMgYGBgc3RvcHdvcmRfZW5MaWduZWBgYCBjb21tZSBgYGBzdG9wd29yZHNfYV9yZXRpcmVyYGBgLgpjb3JwdXNfY2xlYW4gPC0gcmVtb3ZlU3RvcFdvcmRzKGNvcnB1c19jbGVhbiwgc3RvcHdvcmRfZW5MaWduZSkKYGBgCgpTJ2lsIHJlc3RlIGRlcyBtb3RzIHF1aSBuZSBtZSBwbGFpc2VudCBwYXMsIGplIHBldXggY29udGludWVyIGRlIGxlcyByZXRpcmVyIGVuIGxlcyBtZXR0YW50IGRhbnMgdW4gdmVjdGV1cgoKYGBge3J9CnN0b3BXb3JkcyA8LSBjKCAiw6BfbGUiLCAiZGVfbGUiLCAiLcOqdHJlIiwgImZhaXJlIiwgImZhbGxvaXIiLCAic2F2b2lyIiwgInBvdXZvaXIiLCAiZGV2b2lyIiwgImRldm9pciIsICJ2b2lyIiwgInZvdWxvaXIiKQpjb3JwdXNfY2xlYW4gPC0gdG1fbWFwKGNvcnB1c19jbGVhbiwgcmVtb3ZlV29yZHMsIHN0b3BXb3JkcykKaW5zcGVjdChjb3JwdXNfY2xlYW5bNl0pCmBgYAoKSmUgZmFpcyBkZSBub3V2ZWF1IHVuZSBtYXRyaWNlICJ0ZXJtZS9kb2N1bWVudCIgKERUTSwgX0RvY3VtZW50LXRlcm0gbWF0cml4XykuIE9uIHNlIHJhcHBlbGxlIHF1J2lsIHMnYWdpdCBkZSBjcsOpZXIgdW5lIG1hdHJpY2UgKHVuIHRhYmxlYXUpIGF2ZWMgdW5lIGNvbG9ubmUgcG91ciBjaGFxdWUgbW90IGRlIG1vbiBjb3JwdXMsIGV0IHVuIHJhbmcgcGFyIHRleHRlIGRlIG1vbiBjb3JwdXMuCgp8ICAgICAgICB8IG1vdDEgfCBtb3QyIHwgbW90MyB8CnwtLS0tLS0tLXwtLS0tLS18LS0tLS0tfC0tLS0tLXwKfCBUZXh0ZTEgfCAgMSAgIHwgIDEyICB8ICA5ICAgfAp8IFRleHRlMiB8ICAxICAgfCAxNTQgIHwgIDQgICB8CgoKYGBge3J9CmR0bSA8LSBEb2N1bWVudFRlcm1NYXRyaXgoY29ycHVzX2NsZWFuKQpyb3duYW1lcyhkdG0pIDwtIHRoZWF0cmUkZ2VucmUKYGBgCgojIyAyLjIgTGVzIG1vdHMgcGV1IGZyw6lxdWVudHMKCkplIHBldXggZMOpc29ybWFpcyBvYnNlcnZlciBsYSBmcsOpcXVlbmNlIGRlcyBtb3RzOiBqZSByZXRyb3V2ZSBsYSBsb2kgZGUgWmlwZiBkYW5zIGxhIGRpc3RyaWJ1dGlvbiBkZSBtZXMgZG9ubsOpZXMKCmBgYHtyfQpmcmVxIDwtIGFzLmRhdGEuZnJhbWUoY29sU3Vtcyhhcy5tYXRyaXgoZHRtKSkpCmNvbG5hbWVzKGZyZXEpIDwtIGMoImZyZXF1ZW5jZSIpCiNDb21tZSBqZSB2YWlzIGRlc3NpbmVyIHVuIGdyYXBoLCBqJ2FpIGJlc29pbiBkJ3VuZSBub3V2ZWxsZSBsaWJyYWlyaWU6IGBgYGdncGxvdDJgYGAKaWYgKCFyZXF1aXJlKCJnZ3Bsb3QyIikpewogIGluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKQogIGxpYnJhcnkoImdncGxvdDIiKQp9CiNKZSBkZXNzaW5lIG1vbiBncmFwaApnZ3Bsb3QoZnJlcSwgYWVzKHg9ZnJlcXVlbmNlKSkgKyBnZW9tX2RlbnNpdHkoKQpgYGAKCkplIHBldXggY29tcHRlciBsZXMgbW90cyBhdmVjIGRlcyBmcsOpcXVlbmNlcyBmYWlibGVzLCBwYXIgZXhlbXBsZSBhdmVjIG1vaW5zIGRlIDEwMCBvY2N1cnJlbmNlcwoKYGBge3J9CiNKZSByZXRpcmUgdG91cyBsZXMgbW90cyBxdWkgYXBwYXJhaXNzZW50IGVudHJlIDAgZXQgNDAwIGZvaXMgKG9uIHBldXQgcmVtcGxhY2VyIDQwMCBwYXIgMTAwLCBvdSBtw6ptZSAxMCBzaSBsZSBjb3JwdXMgZXN0IHRyb3AgZ3JvcykKbW90c1BldUZyZXF1ZW50cyA8LSBmaW5kRnJlcVRlcm1zKGR0bSwgMCwgNDAwKQpsZW5ndGgobW90c1BldUZyZXF1ZW50cykKaGVhZChtb3RzUGV1RnJlcXVlbnRzLDUwKQoKYGBgCgpKZSBwZXV4IGF1c3NpIGNvbXB0ZXIgZXQgYWZmaWNoZXIgbGVzIG1vdHMgbGVzIHBsdXMgZnLDqXF1ZW50cywgcGFyIGV4ZW1wbGUgYXZlYyBwbHVzIGRlIDQwMCBvY2N1cnJlbmNlcwoKYGBge3J9Cm1vdHNUcmVzRnJlcXVlbnRzIDwtIGZpbmRGcmVxVGVybXMoZHRtLCA0MDEsIEluZikKbGVuZ3RoKG1vdHNUcmVzRnJlcXVlbnRzKQpoZWFkKG1vdHNUcmVzRnJlcXVlbnRzLDUwKQpgYGAKCkplIGZhaXMgdW4gdHLDqHMgZ3JhbmQgbcOpbmFnZSwgYXZlYyB1bmUgZm9uY3Rpb24gcXVlIGplIGNyw6llIHBvdXIgcmV0aXJlciBsZXMgbW90cyBsZXMgbW9pbnMgZnLDqXF1ZW50czoKCmBgYHtyfQojSmUgY3LDqWUgdW5lIGZvbmN0aW9uIGBgYGdyYW5kTWVuYWdlYGBgCmdyYW5kTWVuYWdlIDwtIGZ1bmN0aW9uKGNvcnB1c19hX25ldHRveWVyLCBtb3RzX3BldV9pbXBvcnRhbnRzKXsKICAjQWZpbiBkZSBzaW1wbGlmaWVyIGxlIHRyYXZhaWwgKGRlIG1vbiBvcmRpbmF0ZXVyKSwgamUgdmFpcyByYXNzZW1ibGVyIGxlcyBtb3RzIMOgIHJldGlyZXIgZW4gZ3JvdXBlIDUwMCB0b2tlbnMsIHF1ZSBqZSB2YWlzIHRyYWl0ZXIgc8OpcGFyw6ltZW50LgogICAgY2h1bmsgPC0gNTAwCiAgICAjSmUgY29tcHRlIGxlIG5vbWJyZSBkZSBtb3RzIMOgIHJldGlyZXIKICAgIG4gPC0gbGVuZ3RoKG1vdHNfcGV1X2ltcG9ydGFudHMpCiAgICAjSmUgY29tcHRlIGxlcyBncm91cGVzIGRlIDUwMCAoaWNpIDE3LjA1KSwgaidhcnJvbmRpcyBhdSBwbHVzIHBldGl0IGVudGllciBzdXDDqXJpZXVyIChpY2kgMTgpIAogICAgciA8LSByZXAoMTpjZWlsaW5nKG4vY2h1bmspLGVhY2g9Y2h1bmspWzE6bl0KICAgICNKZSBjb25zdGl0dWUgbWVzIGxvdHMgc3VyIGxhIGJhc2UgZHUgZMOpY29tcHRlIHByw6ljw6lkZW1tZW50IG1lbnRpb25uw6kKICAgIGQgPC0gc3BsaXQobW90c19wZXVfaW1wb3J0YW50cyxyKQogICAgI0plIGZhaXMgdW5lIGJvdWNsZTogcG91ciByZXRpcmVyIGxlcyBtb3RzIGR1IGNvcnB1cywgbW9yY2VhdSBwYXIgbW9yY2VhdQogICAgZm9yIChpIGluIDE6bGVuZ3RoKGQpKSB7CiAgICAgICAgY29ycHVzX2FfbmV0dG95ZXIgPC0gdG1fbWFwKGNvcnB1c19hX25ldHRveWVyLCByZW1vdmVXb3JkcywgYyhwYXN0ZShkW1tpXV0pKSkKICAgIH0KICAgICNKZSByZW52b2llIHVuIHLDqXN1bHRhdAogICAgcmV0dXJuKGNvcnB1c19hX25ldHRveWVyKQp9CiMgSid1dGlsaXNlIG1hIGZvbmN0aW9uIGF2ZWMgYGBgY29ycHVzX2NsZWFuYGBgIGNvbW1lIGBgYGNvcnB1c19hX25ldHRveWVyYGBgIGV0IGBgYG1vdHNQZXVGcmVxdWVudHNgYGAgY29tbWUgYGBgbW90c19wZXVfaW1wb3J0YW50c2BgYApjb3JwdXNfY2xlYW4gPC0gZ3JhbmRNZW5hZ2UoY29ycHVzX2NsZWFuLCBtb3RzUGV1RnJlcXVlbnRzKQpgYGAKCkplIHJlZMOpZmluaXMgbWEgbWF0cmljZSDDoCBwYXJ0aXIgZGUgbW9uIG5vdXZlYXUgY29ycHVzCgpgYGB7cn0gCmR0bSA8LSBEb2N1bWVudFRlcm1NYXRyaXgoY29ycHVzX2NsZWFuKQpyb3duYW1lcyhkdG0pIDwtIHRoZWF0cmUkZ2VucmUKZnJlcSA8LSBhcy5kYXRhLmZyYW1lKGNvbFN1bXMoYXMubWF0cml4KGR0bSkpKQpjb2xuYW1lcyhmcmVxKSA8LSBjKCJmcmVxdWVuY2UiKQojSmUgZmFpcyB1biBwZXRpdCBncmFwaApnZ3Bsb3QoZnJlcSwgYWVzKHg9ZnJlcXVlbmNlKSkgKyBnZW9tX2RlbnNpdHkoKQpgYGAKCkplIG5ldHRveWUgdW4gcGV1IG1hIERUTSBwb3VyIMOpbGltaW5lciBsZXMgcmFuZ3MgdmlkZXMKCmBgYHtyfQpyb3dUb3RhbHMgPC0gYXBwbHkoZHRtICwgMSwgc3VtKSAgICAgICNGaW5kIHRoZSBzdW0gb2Ygd29yZHMgaW4gZWFjaCBEb2N1bWVudApkdG1fY2xlYW4gICA8LSBkdG1bcm93VG90YWxzPiAwLCBdICAgICNyZW1vdmUgYWxsIGRvY3Mgd2l0aG91dCB3b3JkcwpgYGAKCiMgMy4gX1RvcGljIG1vZGVsaW5nXwoKKipSZW1hcnF1ZSBwcsOpbGltaW5haXJlKio6IGxlIF90b3BpYyBtb2RlbGluZ18gcmVxdWllcnQgZGVzICh0csOocykgZ3JhbmRzIGNvcnB1cywgc2kgcG9zc2libGUgZW4gY2VudGFpbmVzIGRlIGRvY3VtZW50cy4gUGFzIGRlIHBhbmlxdWUgY2VwZW5kYW50OiB1bmUgbWFuacOocmUgZGUgbGVzIG9idGVuaXIgZXN0IGRlIGRpdmlzZXIgY2hhcXVlIHRleHRlcyBlbiBwbHVzaWV1cnMgZG9jdW1lbnRzIHF1aSBmb3JtZW50IHVuZSB1bml0w6kgc8OpbWFudGlxdWUuIFBhciBleGVtcGxlIGxlIGNoYXBpdHJlLCBsYSBzY8OobmUsIGxlIHBhcmFncmFwaGUsIG91IGJpZW4gKGNvbW1lIGMnZXN0IGxlIGNhcyBwb3VyIG5vdHJlIGV4ZXJjaWNlKSBkZSAxMDAwIG1vdHMuCgojIyAzLjEgRXhwbGljYXRpb24gdGjDqW9yaXF1ZQoKVW4gdGjDqG1lIChfdG9waWNfKSBlc3QgdW4gX2NsdXN0ZXJzXyBkZSBtb3RzLCBfaS5lLl8gdW5lIHLDqWN1cnJlbmNlIGRlIGNvLW9jY3VycmVuY2UuCgohWzEwMCUgY2VudGVyXShpbWFnZXMvY28tb2NjdXJyZW5jZS5wbmcpCgpTb3VyY2U6IFtXaWtpc291cmNlXShodHRwczovL2NvbW1vbnMud2lraW1lZGlhLm9yZy93aWtpL0ZpbGU6S2hjb2Rlcl9uZXRfZS5wbmcpCgpMZSBwcmluY2lwZSBkdSBfdG9waWMgbW9kZWxpbmdfIGVzdCBwcm9jaGUgZGUgY2VsdWkgZGUgc3VybGlnbmVyIHVuIHRleHRlIGF2ZWMgcGx1c2lldXJzIGNvdWxldXJzOiB1bmUgcG91ciBjaGFxdWUgc3VqZXQsIHRow6htZSBvdSBfdG9waWNfLgoKIVsxMDAlIGNlbnRlcl0oaW1hZ2VzL3NvdWxpZ25lci5wbmcpCgpVbmUgdGVsbGUgaW1hZ2Ugc291bMOodmUgZGV1eCBxdWVzdGlvbnMgc3VyIGxlc3F1ZWxsZXMgbm91cyByZXZpZW5kcm9udCBwbHVzIHRhcmQ6CiogdW4gYXJ0aWNsZSBwZXV0LWlsIGNvbnRlbmlyIHBsdXNpZXVycyBzdWpldHM/CiogdW4gbW90IHBldXQtaWwgbidhcHBhcnRlbmlyIHF1J8OgIHVuIHNldWwgc3VqZXQ/CgpBZmluIGRlIHJlY29ubmHDrnRyZSBjZXMgc3VqZXRzLCBvbiB2YSByZWNvdXJpciDDoCB1bmUgYWxsb2NhdGlvbiBkZSBEaXJpY2hsZXQgbGF0ZW50ZSAoIF9MYXRlbnQgRGlyaWNobGV0IGFsbG9jYXRpb25fLCBMREEpLgoqIEMnZXN0IHVuZSBhcHByb2NoZSBub24gc3VwZXJ2aXPDqWUsIGMnZXN0LcOgLWRpcmUgcXUnZWxsZSBuZSBuw6ljZXNzaXRlIHBhcyBkJ2Fubm90YXRpb24gcHLDqWFsYWJsZSBkZSBkb25uw6llcy4KKiBJbCBub3VzIGZhdXQgZMOpZmluaXIgw6AgbCdhdmFuY2UgdW4gbm9tYnJlIGRlIHN1amV0cy90aMOobWVzIChfaW5mcmFfIGxhIHZhcmlhYmxlIGBrYCkKCkxlIF9MREFfICBlc3QgbW9kw6hsZSBnw6luw6lyYXRpZiBwcm9iYWJpbGlzdGUgcGVybWV0dGFudCBk4oCZZXhwbGlxdWVyIGRlcyBlbnNlbWJsZXMgZOKAmW9ic2VydmF0aW9ucywgcGFyIGxlIG1veWVuIGRlIGdyb3VwZXMgbm9uIG9ic2VydsOpcywgZXV4LW3Dqm1lcyBkw6lmaW5pcyBwYXIgZGVzIHNpbWlsYXJpdMOpcyBkZSBkb25uw6llcy4KCiFbMTUwJSBjZW50ZXJdKGltYWdlcy9MREFfZm9ybXVsYS5wbmcpCgpTb3VyY2U6IFt3aWtpcGVkaWFdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0xhdGVudF9EaXJpY2hsZXRfYWxsb2NhdGlvbikKCkRhbnMgY2UgZ3JhcGg6CiogX01fIGVzdCBsZSBub21icmUgZGUgZG9jdW1lbnRzIChjb3JwdXMpCiogX05fIGVzdCBsZSBub21icmUgZGUgbW90cyAoZG9jdW1lbnQpCiogX1dfIGVzdCB1biBtb3Qgb2JzZXJ2w6kKCkxhIHBhcnRpZSBfbGF0ZW50ZV8gKGNhY2jDqWUpOgoqIF9aXyBlc3QgdW4gX3RvcGljXyBhdHRyaWJ1w6kgw6AgdW4gX3dfCiogX864XyBlc3QgbGUgbcOpbGFuZ2UgZGVzIF90b3BpY3NfIMOgIGwnw6ljaGVsbGUgZHUgZG9jdW1lbnQKCkRldXggcGFyYW3DqHRyZXMgcG91ciBsYSBkaXN0cmlidXRpb24KKiBfzrFfIGVzdCBsYSBkaXN0cmlidXRpb24gcGFyIGRvY3VtZW50LiBTaSBzYSB2YWxldXIgZXN0IMOpbGV2w6llLCBsZSBkb2N1bWVudCB0ZW5kIMOgIGNvbnRlbmlyIHBsdXNpZXVycyBfdG9waWNzXywgc2kgbGEgdmFsZXVyIGVzdCBmYWlibGUgbGUgbm9tYnJlIGRlIF90b3BpY3NfIGVzdCBsaW1pdMOpIAoqIF/Osl8gZXN0IGxhIGRpc3RyaWJ1dGlvbiBwYXIgX3RvcGljXy4gU2kgc2EgdmFsZXVyIGVzdCDDqWxldsOpZSwgdW4gbcOqbWUgbW90IHNlIHJldHJvdXZlIGRhbnMgcGx1c2lldXJzIF90b3BpY3NfIChxdWkgc2UgcmVzc2VtYmxlbnQgZG9uYyksIHNpIGxhIHZhbGV1ciBlc3QgZmFpYmxlIGxlcyBzaW1pbGFyaXTDqXMgZW50cmUgbGVzIF90b3BpY3NfIGVzdCBmYWlibGUgCgohWzE1MCUgY2VudGVyXShpbWFnZXMvTERBX2Rlc3Npbi5wbmcpCgpTb3VyY2U6IFt3aWtpcGVkaWFdKGh0dHBzOi8vY29tbW9ucy53aWtpbWVkaWEub3JnL3dpa2kvRmlsZTpMYXRlbnRfRGlyaWNobGV0X2FsbG9jYXRpb24uc3ZnKQoKIyMgMy4yIFVuZSBMREEKCkxlIG1vZMOobGUgdmEgY2xhc3NlciBhbMOpYXRvaXJlbWVudCB0b3VzIGxlcyBtb3RzIGVuIF9uXyBzdWpldHMsIGV0IHRlbnRlciBkJ2FmZmluZXIgY2V0dGUgcsOpcGFydGl0aW9uIGRlIG1hbmnDqHJlIGl0w6lyYXRpdmUgZW4gb2JzZXJ2YW50IGxlcyBjb250ZXh0ZXM6CgpgYGB7cn0KI0onaW5zdGFsbGUgdW5lIG5vdXZlbGxlIGxpYnJhaXJpZSBwb3VyIGxlIF90b3BpYyBtb2RlbGluZ18KaWYoIXJlcXVpcmUoInRvcGljbW9kZWxzIikpewogIGluc3RhbGwucGFja2FnZXMoInRvcGljbW9kZWxzIikKICBsaWJyYXJ5KCJ0b3BpY21vZGVscyIpCn0KI0plIHZhaXMgcGFydGlyIHN1ciB1bmUgY2xhc3NpZmljYXRpb24gZW4gZGV1eCBfdG9waWNzXwprID0gMgpsZGFfMiA8LSBMREEoZHRtX2NsZWFuLCBrPSBrLCBjb250cm9sID0gbGlzdChzZWVkID0gMTIzNCkpCiMjSmUgdGVudGUgYXZlYyB0cm9pcywgcG91ciB2b2ly4oCmCmxkYV8zIDwtIExEQShkdG1fY2xlYW4sIGs9IGsrMSwgY29udHJvbCA9IGxpc3QoYWxwaGEgPSAwLjEpKQpgYGAKCkxlIHLDqXN1bHRhdCBwcm9kdWl0IGVzdCB1bmUgbWF0cmljZSBhdmVjIHBvdXIgY2hhcXVlIG1vdCBsYSBwcm9iYWJpbGl0w6kgcXUnaWwgYXBwYXJ0aWVubmUgw6AgdW4gZGVzIGRpZmbDqXJlbnRzIF90b3BpY3NfLiBPbiBkb25uZSB1biBzY29yZSBfzrJfLCBxdWkgZXN0IGNlbHVpIHByw6lzZW50w6kgaW5mcmEuCgpgYGB7cn0KdG9waWNzIDwtIHRpZHkobGRhXzIsIG1hdHJpeCA9ICJiZXRhIikKdG9waWNzCmBgYAoKIyMgMy4zIExlcyBwYXJhbcOodHJlcyBkZSBHaWJicwoKTGVzIHBhcmFtw6h0cmVzIGRlIEdpYmJzIHBlcm1ldHRlbnQgdW5lIHNvcGhpc3RpY2F0aW9uIGR1IHN5c3TDqG1lIHByw6ljw6lkZW50LiBDJ2VzdCB1bmUgcHJvYmFiaWxpdMOpIGNvbmRpdGlvbm5lbGxlIHF1aSBzJ2FwcHVpZSwgcG91ciBjYWxjdWxlciBsZSBfzrJfIGQndW4gbW90LCBzdXIgbGUgX86yXyBkZXMgbW90cyB2b2lzaW5zLiBQb3VyIGNlIGZhaXJlIG5vdXMgZGV2b25zIGTDqXRlcm1pbmVyOgoxLiDDgCBxdWVsIHBvaW50IHVuIGRvY3VtZW50IGFpbWUgdW4gX3RvcGljXwoyLiDDgCBxdWVsIHBvdW50IHVuIF90b3BpY18gYWltZSB1biBtb3QKClVuIGRvY3VtZW50OgoKfCBWb2l0dXJlIHwgQXV0b3JvdXRlIHwgTXVzaXF1ZSB8IFbDqWxvIHwgVmFjYW5jZXMgfAp8LS0tLS0tLS0tfC0tLS0tLS0tLS0tfC0tLS0tLS0tLXwtLS0tLS18LS0tLS0tLS0tLXwKfCAgICAxICAgIHwgICAgPz8gICAgIHwgICAgMiAgICB8ICAxICAgfCAgICAgMyAgICB8CgpTYWNoYW50IHF1ZSBsZSBkw6ljb21wdGUgZXN0IGxlIHN1aXZhbnQKCnwgICAgICAgICAgIHwgdG9waWMgMSB8IHRvcGljIDIgfCB0b3BpYyAzIHwKfC0tLS0tLS0tLS0tfC0tLS0tLS0tLXwtLS0tLS0tLS18LS0tLS0tLS0tfAp8IFZvaXR1cmUgICB8ICAgMzQgICAgfCAgIDQ5ICAgIHwgICAgNzUgICB8CnwgQXV0b3JvdXRlIHwgICAxNTAgICB8ICAgNTAgICAgfCAgICA3MCAgIHwKfCBNdXNpcXVlICAgfCAgIDM0ICAgIHwgICAgNCAgICB8ICAgMTcwICAgfAp8IFbDqWxvICAgICAgfCAgIDU0MyAgIHwgICAgMiAgICB8ICAgMTUwICAgfAp8IFZhY2FuY2VzICB8ICAgMjMgICAgfCAgIDcwICAgIHwgICA1NjMgICB8CgpMZSBfdG9waWNfIDEgZXN0IGxlIHBsdXMgcmVwcsOpc2VudMOpIGRhbnMgbGUgZG9jdW1lbnQsIGV0IF9BdXRvcm91dGVfIGVzdCBkw6lqw6Agc3VycmVwcsOpc2VudMOpIGRhbnMgbGUgZMOpY29tcHRlLCBkb25jIG9uIF91cGRhdGVfIGxlIHRvdXQKCnwgVm9pdHVyZSB8IEF1dG9yb3V0ZSB8IE11c2lxdWUgfCBWw6lsbyB8IFZhY2FuY2VzIHwKfC0tLS0tLS0tLXwtLS0tLS0tLS0tLXwtLS0tLS0tLS18LS0tLS0tfC0tLS0tLS0tLS18CnwgICAgMSAgICB8ICAgKioxKiogICB8ICAgIDIgICAgfCAgMSAgIHwgICAgIDMgICAgfAoKfCAgICAgICAgICAgfCB0b3BpYyAxIHwgdG9waWMgMiB8IHRvcGljIDMgfAp8LS0tLS0tLS0tLS18LS0tLS0tLS0tfC0tLS0tLS0tLXwtLS0tLS0tLS18CnwgVm9pdHVyZSAgIHwgICAzNCAgICB8ICAgNDkgICAgfCAgICA3NSAgIHwKfCBBdXRvcm91dGUgfCAqKjE1MSoqIHwgICA1MCAgICB8ICAgIDcwICAgfAp8IE11c2lxdWUgICB8ICAgMzQgICAgfCAgICA0ICAgIHwgICAxNzAgICB8CnwgVsOpbG8gICAgICB8ICAgNTQzICAgfCAgICAyICAgIHwgICAxNTAgICB8CnwgVmFjYW5jZXMgIHwgICAyMyAgICB8ICAgNzAgICAgfCAgIDU2MyAgIHwKCmBgYHtyfQojIyBTZXQgcGFyYW1ldGVycyBmb3IgR2liYnMgc2FtcGxpbmcKI0xlIG1vZMOobGUgdmEgdG91cm5lciAyMDAwIGZvaXMgYXZhbnQgZGUgY29tbWVuY2VyIMOgIGVucmVnaXN0cmVyIGxlcyByw6lzdWx0YXRzCmJ1cm5pbiA8LSAyMDAwCiNBcHLDqHMgY2VsYSBpbCB2YSBlbmNvcmUgdG91cm5lciAyMDAwIGZvaXMKaXRlciA8LSAyMDAwCiMgSWwgbmUgdmEgZW5yZWdpc3RyZXIgbGUgcsOpc3VsdGF0IHF1ZSB0b3V0ZXMgbGVzIDUwMCBpdMOpcmF0aW9ucwp0aGluIDwtIDUwMAojc2VlZCBldCBuc3RhcnQgcG91ciBsYSByZXByb2R1Y3RpYmlsaXTDqQpTRUVEPWMoMSwgMiwgMywgNCwgNSkKc2VlZCA8LVNFRUQKbnN0YXJ0IDwtIDUKI1NldWwgbWVpbGxldXIgbW9kw6hsZSBlc3QgdXRpbGlzw6kKYmVzdCA8LSBUUlVFCiMyIHRvcGljcwpsZGFfZ2liYnNfMiA8LSBMREEoZHRtX2NsZWFuLCBrLCBtZXRob2Q9IkdpYmJzIiwgY29udHJvbD1saXN0KG5zdGFydD1uc3RhcnQsIHNlZWQ9c2VlZCwgYmVzdD1iZXN0LCBidXJuaW49YnVybmluLCBpdGVyPWl0ZXIsIHRoaW49dGhpbikpCiMzIHRvcGljcwpsZGFfZ2liYnNfMyA8LSBMREEoZHRtX2NsZWFuLCBrKzEsIG1ldGhvZD0iR2liYnMiLCBjb250cm9sPWxpc3QobnN0YXJ0PW5zdGFydCwgc2VlZD1zZWVkLCBiZXN0PWJlc3QsIGJ1cm5pbj1idXJuaW4sIGl0ZXI9aXRlciwgdGhpbj10aGluKSkKYGBgCgpKZSBwZXV4IGTDqXNvcm1haXMgdm9pciBsZXMgcHJlbWllcnMgcsOpc3VsdGF0cyBwb3VyIGNoYWN1biBkZXMgbW9kw6hsZXMuIElsIHMnYWdpdCBkZSBkZSBtb3RzIGRvbnQgbGEgZnLDqXF1ZW5jZSBkJ3V0aWxpc2F0aW9uIGVzdCBjb3Jyw6lsw6llCgpgYGB7cn0KIkxEQSAyIgp0ZXJtc1RvcGljIDwtIGFzLmRhdGEuZnJhbWUodGVybXMobGRhXzIsMTApKQpoZWFkKHRlcm1zVG9waWMsMTEpCiJMREEgMyIKdGVybXNUb3BpYyA8LSBhcy5kYXRhLmZyYW1lKHRlcm1zKGxkYV8zLDEwKSkKaGVhZCh0ZXJtc1RvcGljLDExKQoiTERBIEdJQkJTIDIiCnRlcm1zVG9waWMgPC0gYXMuZGF0YS5mcmFtZSh0ZXJtcyhsZGFfZ2liYnNfMiwxMCkpCmhlYWQodGVybXNUb3BpYywxMSkKIkxEQSBHSUJCUyAzIgp0ZXJtc1RvcGljIDwtIGFzLmRhdGEuZnJhbWUodGVybXMobGRhX2dpYmJzXzMsMTApKQpoZWFkKHRlcm1zVG9waWMsMTEpCmBgYAoKTm91cyBhbGxvbnMgdXRpbGlzZXIgYGBgbGRhX2dpYmJzXzJgYGAgZXQgY29uc3RydWlyZSB1bmUgbWF0cmljZSBhdmVjIGxlcyBfzrJfIGRlcyB0b2tlbnMgKHBvdXIgbGVzIMmjLCBldCBkb25jIGRlcyBwcm9iYWJpbGl0w6lzIHBhciBkb2N1bWVudCwgb24gYXVyYWl0IG1pcyBgYGBtYXRyaXggPSAiZ2FtbWEiYGBgKS4gQ2hhcXVlIHRva2VuIGVzdCByw6lww6l0w6kgZGV1eCBmb2lzLCBhdmVjIHVuZSBwcm9iYWJpbGl0w6kgcG91ciBjaGFxdWUgX3RvcGljXzoKCmBgYHtyfQp0b3BpY3MgPC0gdGlkeShsZGFfZ2liYnNfMiwgbWF0cml4ID0gImJldGEiKQp0b3BpY3MKYGBgCgojIDQuIFZpc3VhbGlzYXRpb24KCmBgYHtyfQojSmUgdmFpcyBlbmNvcmUgc29sbGljaXRlciB1bmUgbm91dmVsbGUgbGlicmFpcmllCmlmICghcmVxdWlyZSgiZHBseXIiKSl7CiAgIGluc3RhbGwucGFja2FnZXMoImRwbHlyIikKICBsaWJyYXJ5KCJkcGx5ciIpCn0KCiNKZSByw6ljdXDDqHJlIG1lcyBtb3RzCnRvcF90ZXJtcyA8LSB0b3BpY3MgJT4lCiAgZ3JvdXBfYnkodG9waWMpICU+JQogIHRvcF9uKDEwLCBiZXRhKSAlPiUKICB1bmdyb3VwKCkgICU+JQogIGFycmFuZ2UodG9waWMsIC1iZXRhKQojSmUgZmFpcyB1biBncmFwaAp0b3BfdGVybXMgJT4lCiAgbXV0YXRlKHRlcm0gPSByZW9yZGVyX3dpdGhpbih0ZXJtLCBiZXRhLCB0b3BpYykpICU+JQogIGdncGxvdChhZXModGVybSwgYmV0YSwgZmlsbCA9IGZhY3Rvcih0b3BpYykpKSArIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmYWNldF93cmFwKH4gdG9waWMsIHNjYWxlcyA9ICJmcmVlIikgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvb3JkX2ZsaXAoKSArCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfeF9yZW9yZGVyZWQoKQpgYGAKCkplIHZhaXMgZMOpc29ybWFpcyBhc3NvY2llciBjaGFxdWUgbW90IMOgIGwndW4gZGVzIDUgZ2VucmVzIHBvc3NpYmxlcywgcG91ciBkw6l0ZXJtaW5lciBhdXF1ZWwgbWVzIHRva2VucyBzb250IHJhdHRhY2jDqXMsIGV0IGTDqWNvdXZyaXIgKHBvdGVudGllbGxlbWVudCBxdWVsIGdlbnJlIHNlIGNhY2hlciBkZXJyacOocmUgcXVlbCBfdG9waWNfCgpgYGB7cn0KaWYgKCFyZXF1aXJlKCJyZXNoYXBlMiIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJyZXNoYXBlMiIpCiAgbGlicmFyeSgicmVzaGFwZTIiKQp9CmRmIDwtIG1lbHQoYXMubWF0cml4KGR0bV9jbGVhbikpCmRmIDwtIGRmW2RmJFRlcm1zICVpbiUgZmluZEZyZXFUZXJtcyhkdG1fY2xlYW4sIGxvd2ZyZXEgPSA4MDApLCBdCmdncGxvdChkZiwgYWVzKGFzLmZhY3RvcihEb2NzKSwgVGVybXMsIGZpbGw9bG9nKHZhbHVlKSkpICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VvbV90aWxlKCkgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB4bGFiKCJHZW5yZXMiKSArCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlX2ZpbGxfY29udGludW91cyhsb3c9IiNGRUU2Q0UiLCBoaWdoPSIjRTY1NTBEIikgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT05MCwgaGp1c3Q9MSkpCmBgYAoKYGBge3IsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD0xMn0KdHQgPC0gcG9zdGVyaW9yKGxkYV9naWJic18yKSR0ZXJtcwptZWx0ZWQgPSBtZWx0KHR0WyxmaW5kRnJlcVRlcm1zKGR0bV9jbGVhbiwgMTAwMCwxMDAwMCldKQoKY29sbmFtZXMobWVsdGVkKSA8LSBjKCJUb3BpY3MiLCAiVGVybXMiLCAidmFsdWUiKQptZWx0ZWQkVG9waWNzIDwtIGFzLmZhY3RvcihtZWx0ZWQkVG9waWNzKQpnZ3Bsb3QoZGF0YSA9IG1lbHRlZCwgYWVzKHg9VG9waWNzLCB5PVRlcm1zLCBmaWxsPXZhbHVlKSkgKyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlb21fdGlsZSgpICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0zNSkpCmBgYAoKYGBge3IsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD0xMn0KdHQgPC0gcG9zdGVyaW9yKGxkYV9naWJic18zKSR0ZXJtcwptZWx0ZWQgPSBtZWx0KHR0WyxmaW5kRnJlcVRlcm1zKGR0bV9jbGVhbiwgMTAwMCwxMDAwMCldKQoKY29sbmFtZXMobWVsdGVkKSA8LSBjKCJUb3BpY3MiLCAiVGVybXMiLCAidmFsdWUiKQptZWx0ZWQkVG9waWNzIDwtIGFzLmZhY3RvcihtZWx0ZWQkVG9waWNzKQpnZ3Bsb3QoZGF0YSA9IG1lbHRlZCwgYWVzKHg9VG9waWNzLCB5PVRlcm1zLCBmaWxsPXZhbHVlKSkgKyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlb21fdGlsZSgpICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0zNSkpCmBgYAoKYGBge3J9CkRvY3VtZW50VG9waWNQcm9iYWJpbGl0aWVzIDwtIGFzLmRhdGEuZnJhbWUobGRhX2dpYmJzXzJAZ2FtbWEpCnJvd25hbWVzKERvY3VtZW50VG9waWNQcm9iYWJpbGl0aWVzKSA8LSByb3duYW1lcyhjb3JwdXNfY2xlYW4pCmhlYWQoRG9jdW1lbnRUb3BpY1Byb2JhYmlsaXRpZXMpCmBgYAoKCk5vdXMgYWxsb25zIGTDqXNvcm1haXMgZmFpcmUgZGVzIF93b3JkIGNsb3Vkc18uIFBvdXIgY2VsYSBhcHBlbG9ucyAoaW5zdGFsbG9ucz8pIGxlcyBsaWJyYXJpZXMgc3VpdmFudGVzOgoKYGBge3J9CmlmICghcmVxdWlyZSgid29yZGNsb3VkIikpewogICBpbnN0YWxsLnBhY2thZ2VzKCJ3b3JkY2xvdWQiKQogIGxpYnJhcnkoIndvcmRjbG91ZCIpCn0KaWYgKCFyZXF1aXJlKCJSQ29sb3JCcmV3ZXIiKSl7CiAgIGluc3RhbGwucGFja2FnZXMoIlJDb2xvckJyZXdlciIpCiAgbGlicmFyeSgiUkNvbG9yQnJld2VyIikKfQppZiAoIXJlcXVpcmUoIndvcmRjbG91ZDIiKSl7CiAgIGluc3RhbGwucGFja2FnZXMoIndvcmRjbG91ZDIiKQogIGxpYnJhcnkoIndvcmRjbG91ZDIiKQp9CmBgYApqZSByw6ljdXDDqHJlIGxlcyBtb3RzIGV0IGplIGxlcyBhc3NvY2llIMOgIGxldXIg8J2bgwoKYGBge3IsIGZpZy53aWR0aD0yMCwgZmlnLmhlaWdodD0yMH0KdG0gPC0gcG9zdGVyaW9yKGxkYV9naWJic18yKSR0ZXJtcwpkYXRhID0gZGF0YS5mcmFtZShjb2xuYW1lcyh0bSkpCmhlYWQoZGF0YSkKYGBgCgpKZSBwcm9kdWlzIHVuZSB2aXN1YWxpc2F0aW9uIHBhciBfdG9waWNfCgpgYGB7ciwgZmlnLndpZHRoPTMwLCBmaWcuaGVpZ2h0PTIwfQpmb3IodG9waWMgaW4gc2VxKGspKXsKICAgIGRhdGEkdG9waWMgPC10bVt0b3BpYyxdCiAgICAjdGV4dCh4PTAuNSwgeT0xLCBwYXN0ZSgiViIsdG9waWMsIHNlcD0iIiksY2V4PTAuNikKICAgIHdvcmRjbG91ZCgKICAgICAgd29yZHMgPSBkYXRhJGNvbG5hbWVzLnRtLiwKICAgICAgZnJlcSA9IGRhdGEkdG9waWMsCiAgICAgICNzb3VzIGNlIHNldWlsLCBsZXMgbW90cyBuZSBzZXJvbnQgcGFzIGFmZmljaMOpcwogICAgICBtaW4uZnJlcT0wLjAwMDIsCiAgICAgICNub21icmUgbWF4aW11bSBkZSBtb3RzIMOgIGFmZmljaGVyCiAgICAgIG1heC53b3Jkcz0zMCwKICAgICAgI1NpIGZhdXgsIGVuIG9yZHJlIGNyb2lzc2FudAogICAgICByYW5kb20ub3JkZXI9RkFMU0UsCiAgICAgICMlIGRlIG1vdHMgw6AgOTDCsAogICAgICByb3QucGVyPS4zNSwKICAgICAgI3RhaWxsZSBkdSBncmFwaAogICAgICBzY2FsZT1jKDEwLDEwKSwKICAgICAgI2NvdWxldXJzCiAgICAgIGNvbG9ycyA9IGJyZXdlci5wYWwoNSwgIkRhcmsyIikKICAgICAgIyBpbCBlc3QgcG9zc2libGUgZGUgcmVudHJlciBkaXJlY3RlbWVudCBsZXMgY291bGV1cnMgcXVpIG5vdXMgaW50w6lyZXNzZW50CiAgICAgICNjKCJyZWQiLCAiYmx1ZSIsICJ5ZWxsb3ciLCAiY2hhcnRyZXVzZSIsICJjb3JuZmxvd2VyYmx1ZSIsICJkYXJrb3JhbmdlIikKICAgICkKfQpgYGAKCkZpbmlzc29ucyBhdmVjIHVuIHBldSBkZSBtYXV2YWlzIGdvw7t0LCBncsOiY2UgYXUgcGFja2FnZSBgYGB3b3JkY2xvdWQyYGBgCgpgYGB7ciwgZmlnLndpZHRoPTIwLCBmaWcuaGVpZ2h0PTIwfQp3b3JkY2xvdWQyKGRhdGEgPSBkYXRhLAogICAgICAgICAgc2l6ZT0wLjQsCiAgICAgICAgICBjb2xvcj0gInJhbmRvbS1saWdodCIsCiAgICAgICAgICBiYWNrZ3JvdW5kQ29sb3IgPSAicGluayIsCiAgICAgICAgICBzaGFwZSA9ICdzdGFyJywKICAgICAgICAgIHJvdGF0ZVJhdGlvPTEKICAgICkKYGBgCgojIFJlcm1lcmNpZW1lbnRzCkxlcyBkb25uw6llcyBkJ2VudHJhw65uZW1lbnQgb250IMOpdMOpIGNyw6nDqWVzIHBhciBKQiBDYW1wcyAoRU5DKS4KRGVzIG1vcmNlYXV4IGRlIGNlIHNjcmlwdCAobm90YW1tZW50IHBvdXIgbGUgbmV0dG95YWdlIGRlcyBkb25uw6llcykgcHJvdmllbm5lbnQgZCd1biBjb3VycyBkZSBNYXR0aWEgRWdsb2ZmIChVbmlMKS4=